package com.sky.loveshop.utils;

/**
 * 计算距离工具
 * 百度地理坐标拾取工具： https://api.map.baidu.com/lbsapi/getpoint/index.html
 * @author xcy
 * @version V1.0
 * @description
 * @date 2024/11/3 2:25
 */
public class DistanceUtils {

    /**
     * 使用 Haversine 公式 计算两个经纬度之间的距离
     * Haversine 公式是一种简单但精确的方法,适用于计算短距离。
     * @param lon1 经度1
     * @param lat1 纬度1
     * @param lon2 经度2
     * @param lat2 纬度2
     * @return 距离 单位千米
     */
    public static double getDistanceByHaversine(double lon1, double lat1, double lon2, double lat2) {
        double R = 6371; // 地球半径，单位千米
        double dLat = Math.toRadians(lat2 - lat1);
        double dLon = Math.toRadians(lon2 - lon1);
        double a = Math.sin(dLat / 2) * Math.sin(dLat / 2)
                + Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2))
                * Math.sin(dLon / 2) * Math.sin(dLon / 2);
        double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
        return R * c;
    }

    /**
     * 使用 RawMath 公式 计算两个经纬度之间的距离
     * Vincenty 公式是一种更复杂但更精确的方法,适用于计算长距离。
     * @param lon1 经度1
     * @param lat1 纬度1
     * @param lon2 经度2
     * @param lat2 纬度2
     * @return 距离 单位千米
     */
    public static double getDistanceByRawMath(double lon1, double lat1, double lon2, double lat2) {
        double R = 6371; // 地球半径，单位千米
        double x1 = Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lon1));
        double y1 = Math.cos(Math.toRadians(lat1)) * Math.sin(Math.toRadians(lon1));
        double z1 = Math.sin(Math.toRadians(lat1));

        double x2 = Math.cos(Math.toRadians(lat2)) * Math.cos(Math.toRadians(lon2));
        double y2 = Math.cos(Math.toRadians(lat2)) * Math.sin(Math.toRadians(lon2));
        double z2 = Math.sin(Math.toRadians(lat2));

        double dx = x1 - x2;
        double dy = y1 - y2;
        double dz = z1 - z2;

        return R * Math.sqrt(dx * dx + dy * dy + dz * dz);
    }

    /**
     * 使用 Vincenty 公式 计算两个经纬度之间的距离
     * RawMath 公式是一种简单的几何方法,适用于计算中等距离。
     * @param lon1 经度1
     * @param lat1 纬度1
     * @param lon2 经度2
     * @param lat2 纬度2
     * @return 距离 单位千米
     */
    public static double getDistanceByVincenty(double lon1, double lat1, double lon2, double lat2) {
        double a = 6378137, b = 6356752.314245, f = 1 / 298.257223563; // WGS-84 ellipsoid params
        double L = Math.toRadians(lon2 - lon1);
        double U1 = Math.atan((1 - f) * Math.tan(Math.toRadians(lat1)));
        double U2 = Math.atan((1 - f) * Math.tan(Math.toRadians(lat2)));
        double sinU1 = Math.sin(U1), cosU1 = Math.cos(U1);
        double sinU2 = Math.sin(U2), cosU2 = Math.cos(U2);
        double lambda = L, lambdaP, iterLimit = 100;
        double cosSqAlpha, cos2SigmaM, sinSigma, conjSigma, sigma, sinAlpha, C;
        do {
            double sinLambda = Math.sin(lambda), cosLambda = Math.cos(lambda);
            sinSigma = Math.sqrt((cosU2 * sinLambda) * (cosU2 * sinLambda)
                    + (cosU1 * sinU2 - sinU1 * cosU2 * cosLambda) * (cosU1 * sinU2 - sinU1 * cosU2 * cosLambda));
            if (sinSigma == 0) {
                return 0; // co-incident points
            }
            conjSigma = sinU1 * sinU2 + cosU1 * cosU2 * cosLambda;
            sigma = Math.atan2(sinSigma, conjSigma);
            sinAlpha = cosU1 * cosU2 * sinLambda / sinSigma;
            cosSqAlpha = 1 - sinAlpha * sinAlpha;
            cos2SigmaM = conjSigma - 2 * sinU1 * sinU2 / cosSqAlpha;
            C = f / 16 * cosSqAlpha * (4 + f * (4 - 3 * cosSqAlpha));
            lambdaP = lambda;
            lambda = L + (1 - C) * f * sinAlpha * (sigma + C * sinSigma * (cos2SigmaM + C * conjSigma * (-1 + 2 * cos2SigmaM * cos2SigmaM)));
        } while (Math.abs(lambda - lambdaP) > 1e-12 && --iterLimit > 0);
        double uSq = cosSqAlpha * (a * a - b * b) / (b * b);
        double A = 1 + uSq / 16384 * (4096 + uSq * (-768 + uSq * (320 - 175 * uSq)));
        double B = uSq / 1024 * (256 + uSq * (-128 + uSq * (74 - 47 * uSq)));
        double deltaSigma = B * sinSigma * (cos2SigmaM + B / 4 * (conjSigma * (-1 + 2 * cos2SigmaM * cos2SigmaM)
                - B / 6 * cos2SigmaM * (-3 + 4 * sinSigma * sinSigma) * (-3 + 4 * cos2SigmaM * cos2SigmaM)));
        double distance = b * A * (sigma - deltaSigma);
        return distance;
    }

    public static void main(String[] args) {
        // 东山北一巷一号西 113.853745,22.599178
        // 固戍南昌公园沙边 113.847796,22.602131
        double distanceByHaversine1 = getDistanceByHaversine(113.853745, 22.599178, 113.847796, 22.602131);
        System.out.println("两点之间的距离 = " + distanceByHaversine1 + " 千米");

        // 东山北一巷一号西 113.853745,22.599178
        // 宝安中心壹方城   113.893601,22.55934
        double distanceByHaversine2 = getDistanceByHaversine(113.853745, 22.599178, 113.893601, 22.55934);
        System.out.println("两点之间的距离 = " + distanceByHaversine2 + " 千米");


        // 东山北一巷一号西 113.853745,22.599178
        // 南山后海创投大厦 113.944405,22.528054
        double distanceByHaversine3 = getDistanceByHaversine(113.853745, 22.599178, 113.944405, 22.528054);
        System.out.println("两点之间的距离 = " + distanceByHaversine3 + " 千米");
    }

}
